package jannex.sql.extensions;

import jannex.Jannex;
import jannex.internal.ExceptionFactory;
import jannex.sql.JxResultSetMetaData;

import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;

public class ResultSetMetaDataImpl implements JxResultSetMetaData {

    private final ResultSetMetaData resultSetMetaData;

    private Map<String, Integer> cachedResultSetMetaData;
    private Map<String, Integer> cachedCaseSensitive;

    public ResultSetMetaDataImpl(ResultSetMetaData resultSetMetaData, Jannex jannex) {
        this.resultSetMetaData = resultSetMetaData;
    }

    private synchronized Map<String, Integer> map() {
        // TODO use LabelMap instead
        if (cachedResultSetMetaData != null) {
            return cachedResultSetMetaData;
        }
        cachedResultSetMetaData = new LinkedHashMap<>();
        cachedCaseSensitive = new HashMap<>();
        try {
            for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {
                String name = resultSetMetaData.getColumnLabel(i+1);
                if (name == null || name.length() == 0) {
                    name = resultSetMetaData.getColumnName(i+1);
                }
                if (name == null || name.length() == 0) {
                    continue;
                }
                cachedResultSetMetaData.put(name, i+1);
                if (!resultSetMetaData.isCaseSensitive(i+1)) {
                    cachedCaseSensitive.put(name.toLowerCase(Locale.ENGLISH), i+1);
                }
            }
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
        return cachedResultSetMetaData;
    }

    @Override
    public boolean isAutoIncrement(String columnLabel) {
        return isAutoIncrement(getColumn(columnLabel));
    }

    @Override
    public boolean isSearchable(String columnLabel) {
        return isSearchable(getColumn(columnLabel));
    }

    @Override
    public boolean isCurrency(String columnLabel) {
        return isCurrency(getColumn(columnLabel));
    }

    @Override
    public int isNullable(String columnLabel) {
        return isNullable(getColumn(columnLabel));
    }

    @Override
    public boolean isSigned(String columnLabel) {
        return isSigned(getColumn(columnLabel));
    }

    @Override
    public int getColumnDisplaySize(String columnLabel) {
        return getColumnDisplaySize(getColumn(columnLabel));
    }

    @Override
    public String getColumnName(String columnLabel) {
        return getColumnName(getColumn(columnLabel));
    }

    @Override
    public String getSchemaName(String columnLabel) {
        return getSchemaName(getColumn(columnLabel));
    }

    @Override
    public int getPrecision(String columnLabel) {
        return getPrecision(getColumn(columnLabel));
    }

    @Override
    public int getScale(String columnLabel) {
        return getScale(getColumn(columnLabel));
    }

    @Override
    public String getTableName(String columnLabel) {
        return getTableName(getColumn(columnLabel));
    }

    @Override
    public String getCatalogName(String columnLabel) {
        return getCatalogName(getColumn(columnLabel));
    }

    @Override
    public int getColumnType(String columnLabel) {
        return getColumnType(getColumn(columnLabel));
    }

    @Override
    public String getColumnTypeName(String columnLabel) {
        return getColumnTypeName(getColumn(columnLabel));
    }

    @Override
    public boolean isReadOnly(String columnLabel) {
        return isReadOnly(getColumn(columnLabel));
    }

    @Override
    public boolean isWritable(String columnLabel) {
        return isWritable(getColumn(columnLabel));
    }

    @Override
    public boolean isDefinitelyWritable(String columnLabel) {
        return isDefinitelyWritable(getColumn(columnLabel));
    }

    @Override
    public String getColumnClassName(String columnLabel) {
        return getColumnClassName(getColumn(columnLabel));
    }

    @Override
    public boolean containsColumnLabel(String columnLabel) {
        return map().containsKey(columnLabel);
    }

    @Override
    public String[] getColumnLabels() {
        return map().keySet().toArray(new String[cachedCaseSensitive.size()]);
    }

    @Override
    public Map<String, Integer> getColumnLabelMap() {
        return map();
    }

    @Override
    public int getColumn(String columnLabel) {
        Integer key = map().get(columnLabel);
        if (key == null) {
            key = cachedCaseSensitive.get(
                    columnLabel.toLowerCase(Locale.ENGLISH));
        }
        if (key == null) {
            return 0;
        }
        return key;
    }

    @Override
    public int getColumnCount() {
        try {
            return resultSetMetaData.getColumnCount();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isAutoIncrement(int column) {
        try {
            return resultSetMetaData.isAutoIncrement(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isCaseSensitive(int column) {
        try {
            return resultSetMetaData.isCaseSensitive(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isSearchable(int column) {
        try {
            return resultSetMetaData.isSearchable(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isCurrency(int column) {
        try {
            return resultSetMetaData.isCurrency(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public int isNullable(int column) {
        try {
            return resultSetMetaData.isNullable(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isSigned(int column) {
        try {
            return resultSetMetaData.isSigned(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public int getColumnDisplaySize(int column) {
        try {
            return resultSetMetaData.getColumnDisplaySize(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public String getColumnLabel(int column) {
        try {
            return resultSetMetaData.getColumnLabel(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public String getColumnName(int column) {
        try {
            return resultSetMetaData.getColumnName(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public String getSchemaName(int column) {
        try {
            return resultSetMetaData.getSchemaName(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public int getPrecision(int column) {
        try {
            return resultSetMetaData.getPrecision(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public int getScale(int column) {
        try {
            return resultSetMetaData.getScale(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public String getTableName(int column) {
        try {
            return resultSetMetaData.getTableName(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public String getCatalogName(int column) {
        try {
            return resultSetMetaData.getCatalogName(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public int getColumnType(int column) {
        try {
            return resultSetMetaData.getColumnType(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public String getColumnTypeName(int column) {
        try {
            return resultSetMetaData.getColumnTypeName(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isReadOnly(int column) {
        try {
            return resultSetMetaData.isReadOnly(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isWritable(int column) {
        try {
            return resultSetMetaData.isWritable(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isDefinitelyWritable(int column) {
        try {
            return resultSetMetaData.isDefinitelyWritable(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public String getColumnClassName(int column) {
        try {
            return resultSetMetaData.getColumnClassName(column);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T unwrap(Class<T> iface) {
        try {
            if (iface.equals(ResultSetMetaData.class)) {
                return (T) resultSetMetaData;
            }
            return resultSetMetaData.unwrap(iface);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) {
        try {
            return iface.equals(ResultSetMetaData.class) || resultSetMetaData.isWrapperFor(iface);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }
}
